from datetime import datetime, timedelta
from typing import List

from sqlalchemy import and_, case, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload

from app.bot.services.date_time import get_date_time
from app.database.enums import OrderPeriod
from app.database.models import Order, Proxy, User
from app.database.services.users import get_user


async def get_orders(session: AsyncSession) -> List[Order]:

    q = select(Order).where(Order.status == "active")

    res = await session.execute(q)

    return res.scalars().all()


async def get_user_orders(
    session: AsyncSession,
    user_id: int,
    orders_status: str = None,
    orders_type: str = "proxy",
) -> List[Order]:

    q = (
        select(Order)
        .outerjoin(Order.user)
        .filter(and_(User.telegram_id == user_id, Order.type == orders_type))
        .order_by(Order.was_created.desc())
    )

    if orders_status:
        q = q.filter(Order.status == orders_status)

    res = await session.execute(q)

    return res.scalars().all()


async def get_order(session: AsyncSession, order_id: int, joined: bool = True) -> Order:

    q = select(Order).where(Order.id == order_id)

    if joined:
        q = q.options(
            joinedload(Order.proxies),
            joinedload(Order.category),
            joinedload(Order.site),
            joinedload(Order.provider),
            joinedload(Order.country),
            joinedload(Order.user),
            joinedload(Order.allowed_ips),
            joinedload(Order.vpn_server),
        )

    res = await session.execute(q)

    return res.scalar()


async def create_order(
    session: AsyncSession,
    type: str,
    user_id: int,
    proxies: list[Proxy],
    country_id: int,
    provider_id: int,
    category_id: int,
    site_id: int,
    summ: int,
    period: str,
    expiring_on: datetime,
) -> Order:

    user = await get_user(session, user_id, joined=True)

    order = Order(
        type=type,
        proxies=proxies,
        country_id=country_id,
        provider_id=provider_id,
        category_id=category_id,
        site_id=site_id,
        period=period,
        user=user,
        summ=summ,
        summ_total=summ,
        expiring_on=expiring_on,
    )
    session.add(order)

    user.balance = user.balance - summ

    for proxy in order.proxies:
        user.proxies.append(proxy)
        proxy.status = "sold"

    await session.commit()

    return order


async def get_expired_order_ids(session: AsyncSession) -> List[int]:

    cur_date = get_date_time(get_datetime=True)

    q = select(Order.id).where(
        and_(
            Order.type != "vpn_router",
            Order.status == "active",
            Order.expiring_on < cur_date,
        )
    )

    res = await session.execute(q)

    return res.scalars().all()


async def get_expiring_orders(
    session: AsyncSession, autorenew: bool = False
) -> list[Order]:
    """
    Получаем список заказов с истекающим сроком действия
    Нужно для рассылки уведомлений, а также для автопродления
    :param bool autorenew: Получаем заказы с включенным автообновлением
    """

    cur_date = get_date_time(get_datetime=True)

    # Тестовые заказы и заказы с сроком аренды в 4 часа не продлеваются и не уведомляются
    q = (
        select(Order)
        .filter(
            and_(
                Order.type != "vpn_router",
                Order.period != OrderPeriod.four_hours,
                Order.period != OrderPeriod.twenty_minutes,
                Order.status == "active",
            )
        )
        .options(joinedload(Order.user))
    )
    if autorenew:
        # Получаем заказы для автообновления, до конца которых осталось меньше 6 часов
        q = q.filter(
            and_(
                Order.auto_renew == True,
                Order.expiring_on <= cur_date + timedelta(hours=6),
            )
        )
    else:
        # Получаем заказы для рассылки, до конца которых осталось меньше 24 часов
        # За сутки пользователю придет 3 напоминания
        # Заказы с сроком аренды в 1 день уведомляются 1 раз
        q = q.filter(
            and_(
                case(
                    (
                        Order.period == OrderPeriod.one_day,
                        Order.expiring_on <= cur_date + timedelta(hours=4),
                    ),
                    else_=Order.expiring_on <= cur_date + timedelta(hours=24),
                ),
                or_(
                    Order.last_notified <= cur_date - timedelta(hours=8),
                    Order.last_notified == None,
                ),
            )
        )

    res = await session.execute(q)

    return res.scalars().all()


async def set_autorenew(session: AsyncSession, order_id: int, auto_renew: bool) -> None:

    q = update(Order).where(Order.id == order_id).values(auto_renew=auto_renew)

    await session.execute(q)

    await session.commit()
